{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "0",
   "metadata": {},
   "source": [
    "# Import PDK\n",
    "\n",
    "## Importing a PDK from GDS Files  \n",
    "\n",
    "For foundry PDKs, we highly recommend using GDSFactory PDKs. [See available PDKs here](https://gdsfactory.com/).  \n",
    "\n",
    "If your foundry does not yet have a GDSFactory PDK, you can import a PDK from GDS files into GDSFactory. To do this, you will need:  \n",
    "\n",
    "- A GDS file containing all the cells you want to import into the PDK (or multiple GDS files, each containing a separate design).  \n",
    "\n",
    "Ideally, you should also obtain:  \n",
    "\n",
    "- **KLayout layer properties files** – to define the layers available for custom components. This allows you to create a **LayerMap** that associates layer names with `(GDS_LAYER, GDS_PURPOSE)`.  \n",
    "- **Layer stack information** – including material index, thickness, and the Z positions of each layer.  \n",
    "- **DRC rules** – If these are not provided, you can build them using KLayout.  \n",
    "\n",
    "\n",
    "GDS files efficiently describe geometry using **References**, which store each geometry only once in memory. However, most GDS tools lack a clear standard for storing **device metadata** such as settings, port locations, port widths, and port angles.  \n",
    "\n",
    "\n",
    "`gdsfactory` addresses this limitation by storing metadata inside the GDS and provide built-in functions to add pins.  \n",
    "\n",
    "To save a GDS file in `gdsfactory`, use:  \n",
    "\n",
    "```python\n",
    "Component.write_gds()\n",
    "```\n",
    "\n",
    "However, if you want to avoid the port and settings metadata you can use:\n",
    "\n",
    "\n",
    "```python\n",
    "Component.write_gds(with_metadata=False)\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1",
   "metadata": {},
   "outputs": [],
   "source": [
    "import gdsfactory as gf\n",
    "from gdsfactory.config import PATH\n",
    "from gdsfactory.technology import lyp_to_dataclass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.components.mzi()\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3",
   "metadata": {},
   "source": [
    "You can write **GDS**  files."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4",
   "metadata": {},
   "outputs": [],
   "source": [
    "gdspath = c.write_gds(\"extra/mzi.gds\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5",
   "metadata": {},
   "outputs": [],
   "source": [
    "c.pprint_ports()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6",
   "metadata": {},
   "source": [
    "You can import GDS files into gdsfactory thanks to the `import_gds` function.\n",
    "\n",
    "`import_gds` reads the same GDS file from the disk without losing any information. \n",
    "\n",
    "Gdsfactory stores the settings and the ports as part of the GDS metadata."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.import_gds(gdspath)\n",
    "c.pprint_ports()\n",
    "c.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8",
   "metadata": {},
   "source": [
    "### Add ports from pins\n",
    "\n",
    "Sometimes the GDS does not have YAML metadata, therefore you need to figure out the port locations, widths and orientations.\n",
    "\n",
    "gdsfactory provides you with functions that will add ports to the component by looking for the shapes of pins on a specific layer (port_markers or pins).\n",
    "\n",
    "There are different pin standards supported to automatically add ports to components:\n",
    "\n",
    "- PINs towards the inside of the port (port at the outer part of the PIN).\n",
    "- PINs with half of the pin inside and half outside (port at the center of the PIN).\n",
    "- PIN with only labels (no shapes). You have to manually specify the width of the port.\n",
    "\n",
    "\n",
    "Now let us save a GDS and then import it back."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9",
   "metadata": {},
   "outputs": [],
   "source": [
    "c = gf.components.straight()\n",
    "c_with_pins = gf.add_pins.add_pins_container(component=c)\n",
    "c_with_pins.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "10",
   "metadata": {},
   "outputs": [],
   "source": [
    "c_with_pins.ports = []  # Let us rely on the pins to extract the ports.\n",
    "gdspath = c_with_pins.write_gds(\"extra/wg.gds\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "11",
   "metadata": {},
   "outputs": [],
   "source": [
    "c2 = gf.import_gds(gdspath)\n",
    "c2.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "12",
   "metadata": {},
   "outputs": [],
   "source": [
    "c2.ports  # Import_gds does not automatically add the pins."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "13",
   "metadata": {},
   "outputs": [],
   "source": [
    "# This line reads a GDSII file from the location specified by the gdspath variable and converts its geometry into a gdsfactory component.\n",
    "c3 = gf.import_gds(gdspath)\n",
    "\n",
    "# This is a utility function that adds ports to a component by looking for special marker shapes.\n",
    "# It searches the component for small shapes (often squares or paths) on the specified pin_layer (in this case, a layer named \"PORT\").\n",
    "# It then uses the location, width, and orientation of these markers to create and add proper gdsfactory ports to the component.\n",
    "c3 = gf.add_ports.add_ports_from_markers_inside(c3, pin_layer=\"PORT\")\n",
    "c3.plot()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "14",
   "metadata": {},
   "outputs": [],
   "source": [
    "c3.pprint_ports()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "15",
   "metadata": {},
   "source": [
    "Foundries provide PDKs in different formats and commercial tools.\n",
    "\n",
    "The easiest way to import a PDK into gdsfactory is to:\n",
    "\n",
    "1. have each GDS cell import into a separate GDS file.\n",
    "2. have one GDS file with all the cells inside.\n",
    "3. Have a KLayout layermap. Makes it easier to create the layermap.\n",
    "\n",
    "With that you can easily create the PDK as as python package.\n",
    "\n",
    "Thanks to having a gdsfactory PDK as a python package you can:\n",
    "\n",
    "- Version control your PDK using GIT to keep track of changes and work on it as a team.\n",
    "    - Write tests of your pdk components to avoid unwanted changes from one component to another.\n",
    "    - Ensure you maintain the quality of the PDK with continuous integration checks.\n",
    "    - Pin the version of gdsfactory, so new updates of gdsfactory will not affect your code.\n",
    "- Name your PDK version using [semantic versioning](https://semver.org/). For example patches increase the last number (0.0.1 -> 0.0.2).\n",
    "- Install your PDK easily `pip install pdk_fab_a` and easily access an interface with other tools.\n",
    "\n",
    "\n",
    "\n",
    "To create a **Python** package you can start from a customizable template (thanks to cookiecutter).\n",
    "\n",
    "You can create a python package by running these 2 commands inside a terminal:\n",
    "\n",
    "```\n",
    "pip install cookiecutter\n",
    "cookiecutter gh:joamatab/python\n",
    "```\n",
    "\n",
    "It will ask you some questions to fill into the template for the python package.\n",
    "\n",
    "\n",
    "Then you can add the information about the GDS files and the Layers inside that package."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "16",
   "metadata": {},
   "outputs": [],
   "source": [
    "# lyp_to_dataclass(...): This gdsfactory utility function parses the .lyp file and converts its information into the text format of a Python dataclass.\n",
    "print(lyp_to_dataclass(PATH.klayout_lyp))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "17",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let us now create a sample PDK (for demo purposes only) using GDSfactory.\n",
    "# If the PDK is in a commercial tool you can also do this. Make sure you save a single pdk.gds.\n",
    "\n",
    "sample_pdk_cells = gf.grid(\n",
    "    (\n",
    "        gf.components.straight,\n",
    "        gf.components.bend_euler,\n",
    "        gf.components.grating_coupler_elliptical,\n",
    "    )\n",
    ")\n",
    "sample_pdk_cells.write_gds(\"extra/pdk.gds\")\n",
    "sample_pdk_cells"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18",
   "metadata": {},
   "outputs": [],
   "source": [
    "gf.clear_cache()\n",
    "\n",
    "# write_cells_recursively(...): This function recursively goes through the entire hierarchy of the input GDSII file,\n",
    "# finds every unique cell definition, and writes each one to a new .gds file in the output directory.\n",
    "# gdspath=\"extra/pdk.gds\": This is the input file. It is a GDSII file that contains a full design or Process Design Kit (PDK),\n",
    "# which is typically a hierarchical structure of many cells referencing other cells.\n",
    "# # dirpath=\"extra/gds\": This is the output directory. The function will create this folder if it does not exist.\n",
    "gf.write_cells.write_cells_recursively(gdspath=\"extra/pdk.gds\", dirpath=\"extra/gds\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "19",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(gf.write_cells.get_import_gds_script(\"extra/gds\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20",
   "metadata": {},
   "source": [
    "You can also include the code to plot each fix cell in the docstring."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(gf.write_cells.get_import_gds_script(\"extra/gds\", module=\"samplepdk.components\"))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22",
   "metadata": {},
   "source": [
    "## Import PDK from other python packages\n",
    "\n",
    "You can Write the cells into GDS and use the\n",
    "\n",
    "Ideally you also start transitioning your legacy code Pcells into gdsfactory syntax. It is a great way to learn the gdsfactory way!\n",
    "\n",
    "Here is some advice:\n",
    "\n",
    "- Ask your foundry for the gdsfactory PDK.\n",
    "- Leverage the generic pdk cells available in gdsfactory.\n",
    "- Write tests for your cells.\n",
    "- Break the cells into small reusable functions.\n",
    "- Use GIT to track changes.\n",
    "- Review your code with your colleagues and other gdsfactory developers to get feedback. This is key to get better at coding in gdsfactory.\n",
    "- Get rid of any warnings you see."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "23",
   "metadata": {},
   "source": [
    "## Import PDK from YAML uPDK\n",
    "\n",
    "gdsfactory supports read and write to [uPDK YAML definition](https://openepda.org/index.html)\n",
    "\n",
    "Let us write a PDK into uPDK YAML definition and then convert it back to a gdsfactory script.\n",
    "\n",
    "the uPDK extracts the code from the docstrings.\n",
    "\n",
    "```python\n",
    "\n",
    "def evanescent_coupler_sample() -> None:\n",
    "    \"\"\"Evanescent coupler example.\n",
    "\n",
    "    Args:\n",
    "      coupler_length: length of coupling (min: 0.0, max: 200.0, um).\n",
    "    \"\"\"\n",
    "    pass\n",
    "\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "24",
   "metadata": {
    "lines_to_next_cell": 2
   },
   "outputs": [],
   "source": [
    "from gdsfactory.samples.pdk.fab_c import PDK\n",
    "\n",
    "PDK.activate()\n",
    "\n",
    "# The .to_updk() method converts the PDK's settings into a YAML string that follows the universal PDK (uPDK) standard.\n",
    "# The uPDK is an open-source format designed to make PDKs interoperable between different design and simulation tools.\n",
    "yaml_pdk = PDK.to_updk()\n",
    "print(yaml_pdk)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "25",
   "metadata": {},
   "outputs": [],
   "source": [
    "from gdsfactory.component import GDSDIR_TEMP\n",
    "from gdsfactory.read.from_updk import from_updk\n",
    "\n",
    "yamlpath = GDSDIR_TEMP / \"pdk.yml\"\n",
    "yamlpath.write_text(yaml_pdk)\n",
    "gdsfactory_script = from_updk(yamlpath)\n",
    "print(gdsfactory_script)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "26",
   "metadata": {},
   "source": [
    "## Build your own PDK\n",
    "\n",
    "You can create a PDK as a python library using a cookiecutter template. For example, you can use this one:\n",
    "\n",
    "```\n",
    "pip install cookiecutter\n",
    "cookiecutter gh:joamatab/python\n",
    "```\n",
    "\n",
    "Or you can fork the ubcpdk and create new PCell functions that use the correct layers for your foundry. For example:\n",
    "\n",
    "```python\n",
    "\n",
    "from gdsfactory.technology import LayerMap\n",
    "\n",
    "\n",
    "class LayerMap(LayerMap):\n",
    "    WGCORE = (3, 0)\n",
    "    DEVREC: Layer = (68, 0)\n",
    "    PORT: Layer = (1, 10)  # PinRec\n",
    "    PORTE: Layer = (1, 11)  # PinRecM\n",
    "    FLOORPLAN: Layer = (99, 0)\n",
    "\n",
    "    TE: Layer = (203, 0)\n",
    "    TM: Layer = (204, 0)\n",
    "    TEXT: Layer = (66, 0)\n",
    "    LABEL_INSTANCE: Layer = (66, 0)\n",
    "\n",
    "\n",
    "LAYER = LayerMap\n",
    "\n",
    "```"
   ]
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "custom_cell_magics": "kql"
  },
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
